﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using System.Runtime.InteropServices;

namespace Span_Down_Sharp
{
    class Program
    {
        static Program()
        {
            _foreColor = Console.ForegroundColor;
            _backColor = Console.BackgroundColor;

            Console.Title = "批量文件下载器";
        }

        private static ConsoleColor _foreColor;
        private static ConsoleColor _backColor;

        static void Main(string[] args)
        {
            Console.Clear();
            Console.WriteLine("---------------------------------------");
            Console.WriteLine("批量文件下载器 C#/.NET Core 版");
            Console.WriteLine("@author  spany-down-sharp");
            Console.WriteLine("@author  felix");
            Console.WriteLine("@date    2019-04-19");
            Console.WriteLine("---------------------------------------");
            Console.WriteLine();

            var urlFormat = Input.Read_Url("输入URL(含通配符，例如 http://www.spany.com/2019/(*).jpg): ");
            var start = Input.Read_Integer("通配符数字开始(0~200): ", minValue: 0, maxValue: 200);
            var end = Input.Read_Integer($"通配符数字结束({start}~{start + 200}): ", minValue: start, maxValue: start + 200);
            var len = Input.Read_Integer("通配符数字长度(1~5): ", minValue: 1, maxValue: 5);
            var referer = Input.Read_Url("输入Referer为破解防盗链(如果Referer中含有通配符(*)，则将被当前URL替换，如无须Referer则直接回车): ", defaultValue: "https://www.baidu.com/visit");
            Console.WriteLine();

            var urlList = BuildUrlList(urlFormat, start, end, len);
            if (urlList.Count > 0)
            {
                Console.WriteLine("URL列表如下: ");
                foreach (var url in urlList)
                {
                    Console.WriteLine("\t{0}", url);
                }

                Console.WriteLine();

                var yes = Input.Read_YesOrNo($"是否开始下载？(y/n): ");
                if (yes)
                {
                    var path = Input.Read_Path(@"输入文件存储路径(例如 E:\album\travel): ");
                    Console.WriteLine();
                    StartDownload(urlList, path, referer);
                }
            }
            else
            {
                Console.WriteLine("不能创建URL列表，请核对参数！");
            }

            Console.WriteLine();
            Console.Write("按任意键退出……");
            Console.ReadKey(true);
        }

        /// <summary>
        /// 构建URL列表
        /// </summary>
        static IList<string> BuildUrlList(string urlFormat, int start, int end, int len)
        {
            if (!urlFormat.Contains("(*)"))
            {
                return new List<string> { urlFormat };
            }

            IList<string> list = new List<string>();
            for (var i = start; i < end + 1; i++)
            {
                var tmp_url = urlFormat.Replace("(*)", i.ToString("D" + len));
                list.Add(tmp_url);
            }
            return list;
        }

        /// <summary>
        /// 开始下载
        /// </summary>
        static void StartDownload(IList<string> urlList, string path, string referer)
        {
            var lstUserState = new List<DownloadUserState>();
            var watch = Stopwatch.StartNew();
            var cursorTop = Console.CursorTop;
            for (var i = 0; i < urlList.Count; i++)
            {
                referer = referer.Replace("(*)", urlList[i]);
                var localFileName = Path.Combine(path, Path.GetFileName(urlList[i]));
                var userState = new DownloadUserState(urlList[i], localFileName, referer, 30, cursorTop++);
                lstUserState.Add(userState);
                DownloadItem(userState);
            }

            Console.CursorVisible = false;
            while (true)
            {
                if (lstUserState.All(item => item.IsCompleted))
                {
                    watch.Stop();
                    Console.SetCursorPosition(0, cursorTop + 1);
                    var succeed = lstUserState.Count(i => i.IsSuccess);
                    var failed = lstUserState.Count - succeed;
                    var elapsed = Math.Round(watch.ElapsedMilliseconds / 1000.0, 1);
                    Console.WriteLine("总共下载 {0}，成功 {1}, 失败 {2}，耗时 {3}s", lstUserState.Count, succeed, failed, elapsed);
                    break;
                }
                Thread.Sleep(1);
            }
            Console.CursorVisible = true;
        }

        /// <summary>
        /// 下载单个文件
        /// </summary>
        static void DownloadItem(DownloadUserState userState)
        {
            var fileName = Path.GetFileName(userState.LocalFileName);
            Console.Write("文件 {0}", fileName);
            Console.CursorLeft = userState.CursorLeft;
            Console.Write("等待{0}", Environment.NewLine);
            try
            {
                WebClient client = new WebClient();
                client.Headers.Add("User_Agent", "Mozilla/5.0");
                client.Headers.Add("Referer", userState.Referer);
                client.DownloadFileCompleted += client_DownloadFileCompleted;
                client.DownloadProgressChanged += client_DownloadProgressChanged;
                client.DownloadFileAsync(new Uri(userState.UrlAddress), userState.LocalFileName, userState);
            }
            catch (Exception ex)
            {
                userState.Error = ex;
                DisplayFailedDownloadState(userState);
            }
        }

        /// <summary>
        /// 下载进度改变事件
        /// </summary>
        static void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            var userState = e.UserState as DownloadUserState;
            userState.SetProgressValue(e);
            DisplayProgressDownloadState(userState);
        }

        /// <summary>
        /// 下载完成事件
        /// </summary>
        static void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            var userState = e.UserState as DownloadUserState;
            if (e.Error != null)
            {
                userState.Error = e.Error;
                DisplayFailedDownloadState(userState);
            }
            else
            {
                userState.IsSuccess = true;
                if (userState.Percentage != 100)
                {
                    userState.Percentage = 100;
                    DisplayProgressDownloadState(userState);
                }
            }
            userState.IsCompleted = true;
        }

        static readonly object _sync = new object();

        /// <summary>
        /// 显示下载状态（失败时）
        /// </summary>
        static void DisplayFailedDownloadState(DownloadUserState userState)
        {
            lock (_sync)
            {
                Console.SetCursorPosition(userState.CursorLeft, userState.CursorTop);
                Console.ForegroundColor = ConsoleColor.Red;
                Console.Write("下载失败！{0}", userState.Error.Message);
                Console.ForegroundColor = _foreColor;
            }

            if (File.Exists(userState.LocalFileName))
            {
                File.Delete(userState.LocalFileName);
            }
        }

        /// <summary>
        /// 显示下载状态（进度）
        /// </summary>
        static void DisplayProgressDownloadState(DownloadUserState userState)
        {
            lock (_sync)
            {
                Console.SetCursorPosition(userState.CursorLeft, userState.CursorTop);
                Console.Write("{0}KB", Math.Round(userState.TotalBytes / 1024.0));  //258KB
                Console.CursorLeft = userState.CursorLeft + 12;
                if (userState.Percentage == 100)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                    Console.Write("完成".PadRight(20, ' '));
                    Console.ForegroundColor = _foreColor;
                }
                else
                {
                    var receivedBytesDisplay = $"{Math.Round(userState.ReceivedBytes / 1024.0)}KB".PadRight(8, ' ');
                    Console.Write("已下载 {0} {1}%", receivedBytesDisplay, userState.Percentage);  //已下载 220KB 65%
                }
            }
        }        
    }

    /// <summary>
    /// 下载状态
    /// </summary>
    public class DownloadUserState
    {
        public string UrlAddress { get; set; }
        public string LocalFileName { get; set; }
        public string Referer { get; set; }
        public int CursorLeft { get; set; }
        public int CursorTop { get; set; }

        public int Percentage { get; set; }
        public long TotalBytes { get; set; }
        public long ReceivedBytes { get; set; }
        public bool IsSuccess { get; set; }
        public bool IsCompleted { get; set; }
        public Exception Error { get; set; }

        public DownloadUserState() { }
        public DownloadUserState(string urlAddress, string localFileName, string referer, int cursorLeft, int cursorTop)
        {
            this.UrlAddress = urlAddress;
            this.LocalFileName = localFileName;
            this.Referer = referer;
            this.CursorLeft = cursorLeft;
            this.CursorTop = cursorTop;
        }
        public void SetProgressValue(DownloadProgressChangedEventArgs e)
        {
            this.Percentage = e.ProgressPercentage;
            this.TotalBytes = e.TotalBytesToReceive;
            this.ReceivedBytes = e.BytesReceived;
        }
    }
}
